package com.uxsino.commons.http;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.SSLContext;

import com.uxsino.commons.json.Jsons;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.FormBodyPartBuilder;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;

public class Https {

    private String encoding = "UTF-8";

    private HttpClient client;

    private List<Header> headers = Lists.newArrayList();

    private HttpRoutePlanner proxy;

    private HttpClientContext context = HttpClientContext.create();

    public static Https of() {
        Https https = new Https(null, null, null, 80);
        return https;
    }

    public static Https of(Credentials credentials) {
        return new Https(null, null, null, 80).credentials(credentials);
    }

    public static Https of(String encoding) {
        Https https = new Https(null, null, null, 80);
        https.encoding(encoding);
        return https;
    }

    public static Https of(String proxyHost, int port, List<Cookie> cookies, List<Header> headers) {
        Https https = new Https(cookies, headers, proxyHost, port);
        return https;
    }

    public Https credentials(Credentials credentials) {
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, credentials);
        this.credentials(credentialsProvider);
        return this;
    }

    public Https credentials(CredentialsProvider credentialsProvider) {
        this.context.setCredentialsProvider(credentialsProvider);
        return this;
    }

    public Https encoding(String encoding) {
        if (!Strings.isNullOrEmpty(encoding) && Charset.isSupported(encoding)) {
            this.encoding = encoding;
        }
        return this;
    }

    private Https(List<Cookie> cookies, List<Header> headers, String proxyHost, int port) {
        if (headers != null && !headers.isEmpty()) {
            this.headers.addAll(headers);
        }
        this.cookies(cookies);

        // 代理设置
        if (!Strings.isNullOrEmpty(proxyHost)) {
            this.proxy = new HttpRoutePlanner() {
                @Override
                public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context)
                    throws HttpException {
                    return new HttpRoute(target, null, new HttpHost(proxyHost, port),
                        "https".equalsIgnoreCase(target.getSchemeName()));
                }
            };
        }
        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultHeaders(this.headers);
        // 忽略ssl的校验
        try {
            SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(null, (chain, authType) -> true)
                .build();
            builder.setSSLContext(sslContext);
        } catch (Exception e) {
            e.printStackTrace();
        }
        builder.setSSLHostnameVerifier((s, sslSession) -> true);
        // 代理设置
        if (this.proxy != null) {
            builder.setRoutePlanner(this.proxy);
        }
        this.client = builder.build();
    }

    public Https cookies(List<Cookie> cookies) {
        if (cookies != null && !cookies.isEmpty()) {
            if (this.context.getCookieStore() == null) {
                this.context.setCookieStore(new BasicCookieStore());
            }
            cookies.forEach(cookie -> {
                this.context.getCookieStore().addCookie(cookie);
            });
        }
        return this;
    }

    public Response get(String url, Map<String, String> params, List<Cookie> cookies, List<Header> headers) {
        if (Strings.isNullOrEmpty(url)) {
            return Response.of(null);
        }
        HttpGet get = new HttpGet(url(url, "", params));
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(itm -> {
                get.addHeader(itm);
            });
        }
        this.cookies(cookies);

        try {
            Response res = Response.of(this.client.execute(get, this.context));
            return res;
        } catch (Exception e) {
            return Response.of(null);
        }
    }

    public Response get(String url, Map<String, String> params) {
        return get(url, params, null, null);
    }

    public Response get(String url) {
        return get(url, null, null, null);
    }

    public Response post(String url, HttpEntity entity, List<Cookie> cookies, List<Header> headers) {
        if (Strings.isNullOrEmpty(url)) {
            return new Response(null);
        }
        HttpPost post = new HttpPost(url);
        if (headers != null && !headers.isEmpty()) {
            headers.forEach(itm -> {
                post.addHeader(itm);
            });
        }
        this.cookies(cookies);
        if (entity != null) {
            post.setEntity(entity);
        }
        try {
            HttpResponse response = this.client.execute(post, this.context);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY
                    || response.getStatusLine().getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY) {
                String url_redirect = response.getLastHeader("Location").getValue();
                return post(url_redirect, entity, cookies, headers);
            }
            return Response.of(response);
        } catch (Exception e) {
            e.printStackTrace();
            return Response.of(null);
        }
    }

    public Response post(String url, Map<String, Object> params, List<Cookie> cookies, List<Header> headers) {
        HttpEntity entity = null;

        if (params != null && !params.isEmpty()) {
            List<BasicNameValuePair> nvp = Lists.newArrayList();
            // MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            params.forEach((k, v) -> {
                String val = null;
                if(v instanceof  String){
                   val = (String)v;
                }else{
                    val = Jsons.of(v).disableUnicode().toJson();
                }

                val = Strings.nullToEmpty(val);
                if (val.startsWith("\"")) {
                    val = val.substring(1, val.length());
                }
                if (val.endsWith("\"")) {
                    val = val.substring(0, val.length() - 1);
                }
                try {
                    // builder.addPart(FormBodyPartBuilder.create(k, new
                    // StringBody(val,
                    // ContentType.APPLICATION_FORM_URLENCODED.withCharset(encoding))).build());
                    nvp.add(new BasicNameValuePair(k, val));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            try {
                entity = new UrlEncodedFormEntity(nvp, this.encoding);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        return post(url, entity, cookies, headers);
    }

    public Response post(String url, String postContent) {
        return post(url, new ByteArrayEntity(postContent.getBytes()));
    }

    public Response post(String url, HttpEntity entity) {
        return post(url, entity, null, null);
    }

    public Response post(String url, Map<String, Object> params) {
        return post(url, params, null, null);
    }

    public Response post(String url) {
        return post(url, (HttpEntity) null, null, null);
    }

    public Response upload(String url, List<Cookie> cookies, List<Header> headers, Map<String, Object> params,
        String fileBodyNameSpec, File... files) {
        HttpEntity entity = null;

        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        if (params != null && !params.isEmpty()) {
            params.forEach((k, v) -> {
                String val = Jsons.of(v).disableUnicode().toJson();
                val = Strings.nullToEmpty(val);
                if (val.startsWith("\"")) {
                    val = val.substring(1, val.length());
                }
                if (val.endsWith("\"")) {
                    val = val.substring(0, val.length() - 1);
                }
                try {
                    builder.addPart(FormBodyPartBuilder
                        .create(k, new StringBody(val, ContentType.MULTIPART_FORM_DATA.withCharset(encoding))).build());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        if (files != null && files.length > 0) {
            for (File file : files) {
                try {
                    // builder.addBinaryBody(file.getName(), new
                    // FileInputStream(file), ContentType.MULTIPART_FORM_DATA,
                    // file.getName());
                    builder.addPart(FormBodyPartBuilder
                        .create(Strings.isNullOrEmpty(fileBodyNameSpec) ? file.getName() : fileBodyNameSpec,
                            new FileBody(file))
                        .build());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        entity = builder.build();

        return post(url, entity);
    }

    public Response upload(String url, Map<String, Object> params, String fileBodySpecName, File... files) {
        return upload(url, null, null, params, fileBodySpecName, files);
    }

    public Response upload(String url, String... filesPath) {
        List<File> files = Lists.newArrayList();
        for (String fp : filesPath) {
            try {
                files.add(new File(fp));
            } catch (Exception e) {
            }
        }

        return upload(url, (String) null, files.toArray(new File[files.size()]));
    }

    public Response upload(String url, String fileBodySpecName, File... files) {
        return upload(url, null, null, null, fileBodySpecName, files);
    }

    public String url(String host, String uri, Map<String, String> params) {
        StringBuffer buffer = new StringBuffer();
        if (!Pattern.compile("(?i)^http(s{0,1})://.{0,}").matcher(host).matches()) {
            buffer.append("http://");
        }
        buffer.append(host);
        if (!Strings.isNullOrEmpty(uri)) {
            uri = uri.startsWith("/") ? uri.substring(1) : uri;
            if (!host.endsWith("/")) {
                buffer.append("/");
            }
            buffer.append(uri);
        }
        StringBuffer psb = new StringBuffer();
        if (params != null && !params.isEmpty()) {
            params.forEach((k, v) -> {
                if (psb.length() > 0) {
                    psb.append("&");
                }
                try {
                    psb.append(k).append("=").append(URLEncoder.encode(v, encoding));
                } catch (Exception e) {
                }
            });
        }

        if (psb.length() > 0) {
            if (buffer.indexOf("?") > 0) {
                buffer.append("&").append(psb);
            } else {
                buffer.append("?").append(psb);
            }
        }
        return buffer.toString();
    }

    public static class Response {
        private HttpResponse response;

        private int http_code;
        /* private String response_encoding = "ISO-8859-1"; */

        public static Response of(HttpResponse response) {
            return new Response(response);
        }

        public int httpCode() {
            if (this.response != null) {
                this.http_code = response.getStatusLine().getStatusCode();
            }
            return this.http_code;
        }

        private Response(HttpResponse response) {
            this.response = response;
            parseEncoding();
        }

        private void parseEncoding() {
            if (response != null) {
                // TODO 解析网页的编码格式
            }
        }

        public String readString() {
            return readString(null);
        }

        public String readString(String charEncoding) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            readByte(out);
            try {
                if (Strings.isNullOrEmpty(charEncoding)) {
                    return new String(out.toByteArray());
                } else {
                    return new String(out.toByteArray(), charEncoding);
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                return new String(out.toByteArray());
            }
        }

        public void readByte(OutputStream out) {
            if (this.response == null) {
                return;
            }
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {// 请求成功
                System.out.println("【网络请求失败：】返回码" + response.getStatusLine().getStatusCode());
                return;
            }

            HttpEntity entity = this.response.getEntity();
            InputStream inputStream;
            try {
                inputStream = entity.getContent();

                Header header = response.getFirstHeader("Content-Encoding");
                if (header != null && header.getValue().toLowerCase().indexOf("gzip") > -1) {
                    inputStream = new GZIPInputStream(inputStream);
                }

                int readBytes = 0;
                byte[] sBuffer = new byte[512];
                while ((readBytes = inputStream.read(sBuffer)) != -1) {
                    out.write(sBuffer, 0, readBytes);
                    out.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public byte[] readByte() {
            ByteArrayOutputStream ba = new ByteArrayOutputStream();
            readByte(ba);
            return ba.toByteArray();
        }

        public void readFile(FileOutputStream fout) {
            readByte(fout);
        }

        public boolean success() {
            if (this.response == null) {
                return false;
            }
            this.http_code = response.getStatusLine().getStatusCode();
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {// 请求成功
                System.out.println("【网络请求失败：】返回码" + response.getStatusLine().getStatusCode());
                return false;
            }
            return true;
        }
    }
}
